aboutsummaryrefslogtreecommitdiff
path: root/src/routes/projects/[projectId]
diff options
context:
space:
mode:
Diffstat (limited to 'src/routes/projects/[projectId]')
-rw-r--r--src/routes/projects/[projectId]/routine/[routineId]/+page.server.ts21
-rw-r--r--src/routes/projects/[projectId]/routine/[routineId]/+page.svelte186
2 files changed, 125 insertions, 82 deletions
diff --git a/src/routes/projects/[projectId]/routine/[routineId]/+page.server.ts b/src/routes/projects/[projectId]/routine/[routineId]/+page.server.ts
index 83bac3c..f2b6551 100644
--- a/src/routes/projects/[projectId]/routine/[routineId]/+page.server.ts
+++ b/src/routes/projects/[projectId]/routine/[routineId]/+page.server.ts
@@ -31,21 +31,31 @@ export const load: PageServerLoad = async ({ params }) => {
}
const routine = await loadCurrent(routineId);
- if (!routine || routine.archived || routine.projectId !== projectId) {
+ if (!routine || routine.projectId !== projectId) {
throw error(404, 'Routine introuvable');
}
const completions = await loadCompletions(routineId);
+ // An archived routine is frozen at its archive instant: state and chart
+ // stop there instead of now.
+ const at = routine.archivedAt ? Date.parse(routine.archivedAt) : Date.now();
return {
project: { id: project.id, name: project.name },
routine,
completions,
- state: computeRoutineState(routine, completions, Date.now()),
+ state: computeRoutineState(routine, completions, at),
};
};
export const actions: Actions = {
edit: async ({ request, params }) => {
+ const current = await loadCurrent(numericId(params.routineId));
+ if (!current) {
+ return fail(400, { error: 'Routine introuvable' });
+ }
+ if (current.archived) {
+ return fail(400, { error: 'Routine archivée' });
+ }
const data = await request.formData();
const name = parseFormString(data.get('name'));
if (!name) {
@@ -65,6 +75,13 @@ export const actions: Actions = {
},
complete: async ({ request, params }) => {
+ const current = await loadCurrent(numericId(params.routineId));
+ if (!current) {
+ return fail(400, { error: 'Routine introuvable' });
+ }
+ if (current.archived) {
+ return fail(400, { error: 'Routine archivée' });
+ }
const data = await request.formData();
try {
await complete(
diff --git a/src/routes/projects/[projectId]/routine/[routineId]/+page.svelte b/src/routes/projects/[projectId]/routine/[routineId]/+page.svelte
index d57ed79..5f57de4 100644
--- a/src/routes/projects/[projectId]/routine/[routineId]/+page.svelte
+++ b/src/routes/projects/[projectId]/routine/[routineId]/+page.svelte
@@ -42,8 +42,14 @@
year: 'numeric',
});
+ const frozenAt = $derived(
+ data.routine.archived
+ ? Date.parse(data.routine.archivedAt ?? data.routine.createdAt)
+ : Date.now(),
+ );
+
const series = $derived(
- computeHealthSeries(data.routine, data.completions, Date.now()),
+ computeHealthSeries(data.routine, data.completions, frozenAt),
);
const PAD = 10;
@@ -93,65 +99,76 @@
<span class={scoreClass(data.state.health)}>{data.state.health}</span>
</header>
- <form
- class="form-card"
- method="POST"
- action="?/edit"
- use:enhance={() => {
- return async ({ result, update }) => {
- await update({ reset: false });
- };
- }}
- >
- {#if form?.error}
- <p class="error">{form.error}</p>
- {/if}
- <label>
- <span class="form-label">Nom</span>
- <input
- class="form-input"
- type="text"
- name="name"
- bind:value={name}
- autocomplete="off"
- />
- </label>
- <label>
- <span class="form-label">Intervalle</span>
- <input
- class="form-input frequency"
- class:input-invalid={frequency !== '' && frequencyDays === null}
- type="text"
- name="frequencyDays"
- bind:value={frequency}
- placeholder="2w"
- autocomplete="off"
- aria-label="Intervalle (ex: 1d, 2w, 3m)"
- />
- </label>
- <label>
- <span class="form-label">Description</span>
- <textarea
- class="form-input desc"
- name="description"
- bind:value={description}
- rows={3}
- placeholder="Pourquoi, comment, quand..."
- autocomplete="off"
- ></textarea>
- </label>
- {#if frequency !== '' && frequencyDays === null}
- <p class="error">Intervalle invalide — ex : 1d, 2w, 3m, 1y</p>
- {/if}
- <button
- class="btn-primary"
- type="submit"
- disabled={!name.trim() || frequencyDays === null}
+ {#if data.routine.archived}
+ <p class="archived-badge">
+ Archivée le
+ {dateTimeFormatter.format(
+ new Date(data.routine.archivedAt ?? data.routine.createdAt),
+ )}
+ </p>
+ {/if}
+
+ {#if !data.routine.archived}
+ <form
+ class="form-card"
+ method="POST"
+ action="?/edit"
+ use:enhance={() => {
+ return async ({ result, update }) => {
+ await update({ reset: false });
+ };
+ }}
>
- Enregistrer
- </button>
- <p class="save-status" class:visible={saved}>✓ Enregistré</p>
- </form>
+ {#if form?.error}
+ <p class="error">{form.error}</p>
+ {/if}
+ <label>
+ <span class="form-label">Nom</span>
+ <input
+ class="form-input"
+ type="text"
+ name="name"
+ bind:value={name}
+ autocomplete="off"
+ />
+ </label>
+ <label>
+ <span class="form-label">Intervalle</span>
+ <input
+ class="form-input frequency"
+ class:input-invalid={frequency !== '' && frequencyDays === null}
+ type="text"
+ name="frequencyDays"
+ bind:value={frequency}
+ placeholder="2w"
+ autocomplete="off"
+ aria-label="Intervalle (ex: 1d, 2w, 3m)"
+ />
+ </label>
+ <label>
+ <span class="form-label">Description</span>
+ <textarea
+ class="form-input desc"
+ name="description"
+ bind:value={description}
+ rows={3}
+ placeholder="Pourquoi, comment, quand..."
+ autocomplete="off"
+ ></textarea>
+ </label>
+ {#if frequency !== '' && frequencyDays === null}
+ <p class="error">Intervalle invalide — ex : 1d, 2w, 3m, 1y</p>
+ {/if}
+ <button
+ class="btn-primary"
+ type="submit"
+ disabled={!name.trim() || frequencyDays === null}
+ >
+ Enregistrer
+ </button>
+ <p class="save-status" class:visible={saved}>✓ Enregistré</p>
+ </form>
+ {/if}
<h2 class="section-title">Santé</h2>
<div class="form-card chart-card">
@@ -197,27 +214,29 @@
</div>
<h2 class="section-title">Historique</h2>
- <form
- class="form-card form-card--compact add-completion"
- method="POST"
- action="?/complete"
- use:enhance={() => {
- return async ({ update }) => {
- note = '';
- await update({ reset: false });
- };
- }}
- >
- <input
- class="form-input"
- type="text"
- name="note"
- bind:value={note}
- placeholder="Note (optionnel)"
- autocomplete="off"
- />
- <button class="btn-done" type="submit">Fait</button>
- </form>
+ {#if !data.routine.archived}
+ <form
+ class="form-card form-card--compact add-completion"
+ method="POST"
+ action="?/complete"
+ use:enhance={() => {
+ return async ({ update }) => {
+ note = '';
+ await update({ reset: false });
+ };
+ }}
+ >
+ <input
+ class="form-input"
+ type="text"
+ name="note"
+ bind:value={note}
+ placeholder="Note (optionnel)"
+ autocomplete="off"
+ />
+ <button class="btn-done" type="submit">Fait</button>
+ </form>
+ {/if}
<ul class="item-list">
{#each data.completions as c (c.id)}
@@ -305,6 +324,13 @@
border-left: 3px solid var(--accent);
}
+ .archived-badge {
+ margin: 0 0 1rem;
+ color: var(--text-secondary);
+ font-size: 0.875rem;
+ font-weight: 500;
+ }
+
.chart-card {
align-items: stretch;
}